export interface Resolve<V> { (value: V): void }
export interface Reject<R = any> { (reason?: R): void }
export interface Init<V> { (resolve: Resolve<V>, reject: Reject, clear: () => void): void }
interface AnyHolderEventMap<V> {
  clear: CustomEvent<null>;
  create: CustomEvent<V>;
}
export class AnyHolder<V> {
  private _et: HTMLElement = document.createElement('div');
  private _need_clear = false;
  private _value: V | null = null;
  private _pendings: Array<[Resolve<V>, Reject]> = [];
  private _init: Init<V>;
  private _call_resolve: Resolve<V> = (value: V) => {
    this.set_value(value);
    for (const [resolve, reject] of this._pendings)
      try { resolve(value); } catch (e) { reject(e); };
    this._pendings.length = 0;
    if (this._need_clear) { this.set_value(null); this._need_clear = false; }
  };
  private _call_reject: Reject = (reason?: any) => {
    for (const [, reject] of this._pendings) reject(reason);
    this._pendings.length = 0;
    if (this._need_clear) { this.set_value(null); this._need_clear = false; }
  };
  constructor(init: Init<V>) {
    this._init = init;
  }
  on<K extends keyof AnyHolderEventMap<V>>(type: K, listener: (ev: AnyHolderEventMap<V>[K]) => any, options?: boolean | AddEventListenerOptions): () => void;
  on(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | undefined): () => void {
    this._et.addEventListener(type, callback, options);
    return () => this._et.removeEventListener(type, callback, options);
  }

  addEventListener<K extends keyof AnyHolderEventMap<V>>(
    type: K,
    listener: (ev: AnyHolderEventMap<V>[K]) => any,
    options?: boolean | AddEventListenerOptions
  ): void;
  addEventListener(type: string, callback: any, options?: any): void {
    return this._et.addEventListener(type, callback, options);
  }

  dispatch<K extends keyof AnyHolderEventMap<V>>(type: K, detail: AnyHolderEventMap<V>[K]['detail']): boolean {
    return this._et.dispatchEvent(new CustomEvent(type, { detail }));
  }

  removeEventListener<K extends keyof AnyHolderEventMap<V>>(type: K, listener: (ev: AnyHolderEventMap<V>[K]) => any, options?: boolean | AddEventListenerOptions): void;
  removeEventListener(type: string, callback: any, options?: boolean | EventListenerOptions | undefined): void {
    return this._et.addEventListener(type, callback, options);
  }

  get pending() { return !!this._pendings.length; }
  get loaded() { return this._value !== null; }

  get direct(): V | null { return this._value; };
  get value(): Promise<V> {
    const v = this._value;
    if (v) return Promise.resolve(v);
    return new Promise((resolve, reject) => {
      this._pendings.push([resolve, reject]);
      if (this._pendings.length === 1)
        this._init(this._call_resolve, this._call_reject, this.clear);
    });
  }
  private set_value(v: V | null) {
    if (this._value === v) return;
    this.dispatch(v == null ? 'clear' : 'create', v)
    this._value = v;
  }
  readonly clear = () => {
    if (!this._pendings.length) this.set_value(null);
    else this._need_clear = true;
  };
}
